﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using tactics.Abilities;

namespace tactics
{
    using Coord = Utility.Pair<int, int>;
    using HighlightMatrix = Dictionary<int, int>;

    class TacticsRenderer
    {
        Terrain terrain;
        TerrainMap terrainMap;

        GraphicsDevice graphics;

        Effect effect; // shader to apply to terrain
        Texture2D grid; // created grid texture
        Texture2D tiles; // chosen by current map builder xml file

        bool drawGrid;

        #region Properties

        public bool Grid
        {
            get { return drawGrid; }
            set { drawGrid = value; }
        }

        public TerrainMap TerrainMap
        {
            get { return terrainMap; }
            set 
            { 
                terrainMap = value;
                effect = TacticsEngine.Instance.Content.Load<Effect>("Effects/" + terrainMap.Effect);
                string file = TacticsEngine.Instance.Content.RootDirectory + "\\TileSets\\Tiles\\" + terrainMap.TileSetTexture;

                TextureCreationParameters texParams = new TextureCreationParameters(0, 0, 0, 1, SurfaceFormat.Rgba32, TextureUsage.AutoGenerateMipMap, Color.Fuchsia, FilterOptions.Linear, FilterOptions.Linear);

                tiles = Texture2D.FromFile(graphics, file, texParams);

                grid = TacticsEngine.Instance.Content.Load<Texture2D>("Grid");
                //BuildGridTexture(terrainMap.TerrainGridWidth);
            }
        }

        public Terrain Terrain
        {
            get { return terrain; }
            set { terrain = value; }
        }

        #endregion

        public TacticsRenderer(GraphicsDevice graphicsIn)
        {
            graphics = graphicsIn;
            drawGrid = false;
        }

        #region Input Handeling

        public void OnKeyPressed(Keys k)
        {
            if (k == TacticsEngine.Instance.Config.Keys["ToggleGrid"])
                drawGrid = !drawGrid;
        }

        #endregion

        #region Rendering

        private void FillHighlightTexture(int width, int height, Unit selected, Ability ability, HighlightMatrix moveMatrix, HighlightMatrix unitMovePath, Coord highlight, Color highlightColor, Texture2D texture, SelectionType selectionType)
        {
            Color[] areaColour = new Color[width * height];

            // set whole texture to black
            for (int x = 0; x < width; ++x)
            {
                for (int y = 0; y < height; ++y)
                {
                    int i = y * width + x;
                    areaColour[i] = Color.Black;
                }
            }

            if (selected != null && moveMatrix != null)
            {
                if (selectionType == SelectionType.Unit || selectionType == SelectionType.HoverUnit)
                {
                    // set current selected units movement range to white, if applicable
                    if (!selected.Moved)
                        areaColour = HighlightUnitMovement(width, selected, moveMatrix, unitMovePath, highlight, areaColour);
                }
                else if (selectionType == SelectionType.Ability)
                {
                    if (!selected.Acted)
                        areaColour = HighlightAbilityRange(ability, moveMatrix, highlight, areaColour);
                }
            }

            texture.SetData<Color>(areaColour);
        }

        private Color[] HighlightAbilityRange(Ability ability, HighlightMatrix abilityRange, Coord highlight, Color[] abilityHighlight)
        {
            if (ability.HasAoEComponent && highlight.First != -1 && highlight.Second != -1 &&
                abilityRange.Keys.Contains(highlight.Second * terrainMap.Width + highlight.First))
            {
                foreach (var component in ability.Components)
                {
                    if (component.radius > 0)
                    {
                        Coord min = new Coord(highlight.First - component.radius, highlight.Second - component.radius);
                        Coord max = new Coord(highlight.First + component.radius + 1, highlight.Second + component.radius + 1);

                        if (min.First < 0) min.First = 0;
                        if (min.Second < 0) min.Second = 0;
                        if (max.First > terrainMap.Width) max.First = terrainMap.Width;
                        if (max.Second > terrainMap.Height) max.Second = terrainMap.Height;

                        for (int y = min.Second; y < max.Second; ++y)
                            for (int x = min.First; x < max.First; ++x)
                                abilityHighlight[y * terrainMap.Width + x] = Color.Orange;
                    }
                }
            }

            foreach (int key in abilityRange.Keys)
            {
                int type = abilityRange[key];

                if (type == 0)
                {
                    if (abilityHighlight[key] == Color.Black)
                        abilityHighlight[key] = Color.White;
                    else
                        abilityHighlight[key] = Color.OrangeRed;
                }

                if (type == 1)
                    abilityHighlight[key] = Color.Red;
                else if (type == 2)
                    abilityHighlight[key] = Color.Blue;
            }

            return abilityHighlight;
        }

        private static Color[] HighlightUnitMovement(int width, Unit selected, HighlightMatrix moveMatrix, HighlightMatrix movementPath, Coord highlight, Color[] movement)
        {
            float speed = selected.Speed;

            foreach (int key in moveMatrix.Keys)
            {
                float move = moveMatrix[key];

                // 1.01f is used so that the unit square itself won't equal '0' and not drawn
                float shade = 1.01f - (move / speed);
                movement[key] = new Color(shade, shade, shade);
            }

            foreach (int key in movementPath.Keys)
            {
                movement[key] = new Color(0f, 0f, 1f);
            }

            return movement;
        }

        public void Draw(Matrix view, Matrix projection, Unit selected, Ability ability, HighlightMatrix highlightMatrix, HighlightMatrix unitMovePath, Coord highlight, SelectionType selectionType)
        {
            int width = terrainMap.Width;
            int height = terrainMap.Height;

            int primTotal = ((width - 1) * (height - 1)) * 4;

            // state information
            graphics.RenderState.DepthBufferEnable = true;
            graphics.RenderState.DepthBufferWriteEnable = true;
            graphics.RenderState.CullMode = CullMode.CullClockwiseFace;


            graphics.RenderState.AlphaBlendEnable = true;
            graphics.RenderState.AlphaTestEnable = true;
            graphics.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

            graphics.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            graphics.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

            effect.Parameters["View"].SetValue(view);
            effect.Parameters["Projection"].SetValue(projection);
            effect.Parameters["World"].SetValue(Matrix.Identity);

            effect.Parameters["Width"].SetValue(terrainMap.Width);
            effect.Parameters["Height"].SetValue(terrainMap.Height);
            effect.Parameters["Scale"].SetValue(terrainMap.Scale);

            effect.Parameters["LightDirection"].SetValue(new Vector3(0, -1, 1));
            effect.Parameters["LightColour"].SetValue(new Vector3(1.0f, 1.0f, 1.0f));
            effect.Parameters["AmbientColour"].SetValue(new Vector3(0.1f, 0.1f, 0.1f));

            effect.Parameters["GridTex"].SetValue(grid);

            // generate a bitmask to show a given units movement range
            Texture2D highlightTex = new Texture2D(graphics, width, height);
            FillHighlightTexture(width, height, selected, ability, highlightMatrix, unitMovePath, highlight, new Color(0, 0, 1), highlightTex, selectionType);

            effect.Parameters["HighlightTex"].SetValue(highlightTex);
            effect.Parameters["DrawGrid"].SetValue(drawGrid);

            effect.Parameters["TerrainTex"].SetValue(tiles);

            effect.Begin();

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();

                switch (pass.Name)
                {
                    case "Base":
                        graphics.VertexDeclaration = terrain.VertexDeclaration;
                        TacticsEngine.Instance.GraphicsDevice.Vertices[0].SetSource(terrain.VertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                        TacticsEngine.Instance.GraphicsDevice.Indices = terrain.IndexBuffer;
                        TacticsEngine.Instance.GraphicsDevice.DrawIndexedPrimitives(terrain.PrimitiveType, 0, 0, terrain.FullGeometry.Length, 0, primTotal);
                        break;

                    case "Grid":
                        graphics.VertexDeclaration = terrain.GridVertexDeclaration;
                        TacticsEngine.Instance.GraphicsDevice.Vertices[0].SetSource(terrain.GridVertexBuffer, 0, VertexPositionTexture.SizeInBytes);
                        TacticsEngine.Instance.GraphicsDevice.Indices = terrain.GridIndexBuffer;
                        TacticsEngine.Instance.GraphicsDevice.DrawIndexedPrimitives(terrain.GridPrimitiveType, 0, 0, terrain.GridGeometry.Length, 0, primTotal);
                        break;

                    default:
                        break;
                }

                pass.End();
            }

            effect.End();
        }

        #endregion
    }
}
